#include <unistd.h>
#include "service.h"
#include "z_memory.h"
#include "z_logger.h"
#include "aiot_ota_api.h"
#include "ota_private.h"

extern const char *ali_ca_cert;

void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata) {
    uint32_t data_buffer_len = 0;
    uint32_t last_percent = 0;
    int32_t percent = 0;

    /* 目前只支持 packet->type 为 AIOT_DLRECV_HTTPBODY 的情况 */
    if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) {
        return;
    }
    percent = packet->data.percent;

    /* userdata可以存放 demo_download_recv_handler() 的不同次进入之间, 需要共享的数据 */
    /* 这里用来存放上一次进入本回调函数时, 下载的固件进度百分比 */
    if (userdata) {
        last_percent = *((uint32_t *) (userdata));
    }

    data_buffer_len = packet->data.len;

    /* 如果 percent 为负数, 说明发生了收包异常或者digest校验错误 */
    if (percent < 0) {
        log_error("exception: percent = %d\r\n", percent);
        if (userdata) {
            free(userdata);
        }
        return;
    }

    /*
     * TODO: 下载一段固件成功, 这个时候, 用户应该将
     *       起始地址为 packet->data.buffer, 长度为 packet->data.len 的内存, 保存到flash上
     *
     *       如果烧写flash失败, 还应该调用 aiot_download_report_progress(handle, -4) 将失败上报给云平台
     *       备注:协议中, 与云平台商定的错误码在 aiot_ota_protocol_errcode_t 类型中, 例如
     *           -1: 表示升级失败
     *           -2: 表示下载失败
     *           -3: 表示校验失败
     *           -4: 表示烧写失败
     *
     *       详情可见 https://help.aliyun.com/document_detail/85700.html
     */
    log_trace("download %03d%% done, +%d bytes\r\n", percent, data_buffer_len);
    //将保存在buffer中的文件数据存入update.tar.gz文件中，包括release.tar.gz的本体升级文件以及fw_update.ctrl判断脚本
    aiot_download_report_progress(handle, percent);
    FILE *p = fopen("/tmp/update.tar.gz", "a+");
    fwrite(packet->data.buffer, packet->data.len, 1, p);
    fclose(p);

    /* percent 入参的值为 100 时, 说明SDK已经下载固件内容全部完成 */
    if (percent == 100) {
        /*
         * TODO: 这个时候, 一般用户就应该完成所有的固件烧录, 保存当前工作, 重启设备, 切换到新的固件上启动了
                 并且, 新的固件必须要以

                 aiot_ota_report_version(ota_handle, new_version);

                 这样的操作, 将升级后的新版本号(比如1.0.0升到1.1.0, 则new_version的值是"1.1.0")上报给云平台
                 云平台收到了新的版本号上报后, 才会判定升级成功, 否则会认为本次升级失败了
                 如果下载成功后升级失败, 还应该调用 aiot_download_report_progress(handle, -1) 将失败上报给云平台
         */
        int rc = system("tar -zxf /tmp/update.tar.gz -C /usr/data/firmware && rm /tmp/update.tar.gz");
        if (rc < 0) log_error("update command error!");
            //update标志置为开启，执行更新脚本后，重启
        else {
            log_trace("download finished, ready to update.");
            system("fw_setenv recov_ctrl on && /etc/rcS.d/S68recovery start && reboot");
        }
    }

    /* 简化输出, 只有距离上次的下载进度增加5%以上时, 才会打印进度, 并向服务器上报进度 */
    if (percent - last_percent >= 5 || percent == 100) {
        log_trace("download %03d%% done, +%d bytes\r\n", percent, data_buffer_len);
        aiot_download_report_progress(handle, percent);

        if (userdata) {
            *((uint32_t *) (userdata)) = percent;
        }
        if (percent == 100 && userdata) {
            free(userdata);
        }
    }
}

/* 执行aiot_download_recv的线程, 实现固件内容的请求和接收 */
void *demo_ota_download_thread(void *dl_handle) {
    int32_t ret = 0;

    log_trace("starting download thread in 2 seconds ......\r\n");
    sleep(2);

    /* 向固件服务器请求下载 */
    /*
     * TODO: 下面这样的写法, 就是以1个请求, 获取全部的固件内容
     *       设备资源比较少, 或者网络较差时, 也可以分段下载, 需要组合
     *
     *       aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_START, ...);
     *       aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_END, ...);
     *       aiot_download_send_request(dl_handle);
     *
     *       实现, 这种情况下, 需要把以上组合语句放置到循环中, 多次 send_request 和 recv
     *
     */
    aiot_download_send_request(dl_handle);
    while (running) {
        /* 从网络收取服务器回应的固件内容 */
        ret = aiot_download_recv(dl_handle);

        /* 固件全部下载完时, aiot_download_recv() 的返回值会等于 STATE_DOWNLOAD_FINISHED, 否则是当次获取的字节数 */
        if (STATE_DOWNLOAD_FINISHED == ret) {
            printf("download completed\r\n");
            break;
        }
        if (STATE_DOWNLOAD_RENEWAL_REQUEST_SENT == ret) {
            printf("download renewal request has been sent successfully\r\n");
            continue;
        }
        if (ret <= STATE_SUCCESS) {
            printf("download failed, error code is %d, try to send renewal request\r\n", ret);
            continue;
        }
    }

    /* 下载所有固件内容完成, 销毁下载会话, 线程自行退出 */
    aiot_download_deinit(&dl_handle);
    printf("download thread exit\r\n");

    return NULL;
}

static void _ota_recv_handler(void *ota_handle, aiot_ota_recv_t *ota_msg, void *userdata) {
    pthread_t g_download_thread;
    switch (ota_msg->type) {
        case AIOT_OTARECV_FOTA: {
            uint16_t port = 443;
            uint32_t max_buffer_len = 2048;
            aiot_sysdep_network_cred_t cred;
            void *dl_handle = NULL;
            void *last_percent = NULL;

            if (NULL == ota_msg->task_desc) {
                break;
            }

            dl_handle = aiot_download_init();
            if (NULL == dl_handle) {
                break;
            }

            log_trace("OTA target firmware version: %s, size: %u Bytes\r\n", ota_msg->task_desc->version,
                      ota_msg->task_desc->size_total);

            if (NULL != ota_msg->task_desc->extra_data) {
                log_trace("extra data: %s\r\n", ota_msg->task_desc->extra_data);
            }

            memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
            cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA;
            cred.max_tls_fragment = 16384;
            cred.x509_server_cert = ali_ca_cert;
            cred.x509_server_cert_len = strlen(ali_ca_cert);
            uint32_t end = ota_msg->task_desc->size_total / 2;
            /* 设置下载时为TLS下载 */
            aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *) (&cred));
            /* 设置下载时访问的服务器端口号 */
            aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *) (&port));
            /* 设置下载的任务信息, 通过输入参数 ota_msg 中的 task_desc 成员得到, 内含下载地址, 固件大小, 固件签名等 */
            aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *) (ota_msg->task_desc));
            /* 设置下载内容到达时, SDK将调用的回调函数 */
            aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *) (demo_download_recv_handler));
            /* 设置单次下载最大的buffer长度, 每当这个长度的内存读满了后会通知用户 */
            aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *) (&max_buffer_len));
            /* 设置 AIOT_DLOPT_RECV_HANDLER 的不同次调用之间共享的数据, 比如例程把进度存在这里 */
            last_percent = malloc(sizeof(uint32_t));
            if (NULL == last_percent) {
                aiot_download_deinit(&dl_handle);
                break;
            }
            memset(last_percent, 0, sizeof(uint32_t));
            aiot_download_setopt(dl_handle, AIOT_DLOPT_USERDATA, (void *) last_percent);

            /* 启动专用的下载线程, 去完成固件内容的下载 */
            int res = pthread_create(&g_download_thread, NULL, demo_ota_download_thread, dl_handle);
            if (res != 0) {
                log_error("pthread_create demo_ota_download_thread failed: %d\r\n", res);
                aiot_download_deinit(&dl_handle);
                free(last_percent);
            } else {
                /* 下载线程被设置为 detach 类型, 固件内容获取完毕后可自主退出 */
                pthread_detach(g_download_thread);
            }
            break;
        }
        default:
            break;
    }
}

static void *_ota_service_init(aliot_service_args_t *args) {
    void *ota_handle = aiot_ota_init();

    aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, args->mqtt_handle);
    aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, _ota_recv_handler);

    return ota_handle;
}

static void _ota_service_fini(void **handle) {
    aiot_ota_deinit(handle);
}

static char *get_version(char *version) {
    FILE *fp = fopen("/home/.nversion", "r");
    while (!feof(fp)) {
        fscanf(fp, "%s", version);
    }
    fclose(fp);
    return NULL;
}

static void *_ota_service_start(void *handle) {
    char version[MAX_INPUT];
    bzero(version, sizeof(version));
    get_version(version);
    int res = aiot_ota_report_version(handle, version);
    if (res < STATE_SUCCESS) {
        log_error("aiot_ota_report_version failed: -0x%04X\r\n", -res);
    }
    while (running) {
        sleep(1);
    }
    return NULL;
}

static int32_t _ota_service_stop(void *handle) {
    return STATE_SUCCESS;
}

static aliot_service_t _ota_service = {
        .name = OTA_MODULE_NAME,
        .init = _ota_service_init,
        .fini = _ota_service_fini,
        .start = _ota_service_start,
        .stop = _ota_service_stop,
};

aliot_service_t *ota_service_get() {
    return &_ota_service;
}